
# 导入 json 模块，用于处理 JSON 数据
import json
# 导入 os 模块，用于与操作系统进行交互，如文件路径操作
import os
# 导入 uuid 模块，用于生成唯一标识符
import uuid
# 从 inspect 模块导入 getfullargspec 函数，用于获取函数的参数信息
from inspect import getfullargspec

# 从 qtpy 库导入 QtCore 模块，提供核心的非 GUI 功能
from qtpy import QtCore
# 从 qtpy 库导入 QtGui 模块，提供图形和用户界面相关的功能
from qtpy import QtGui
# 从 qtpy.QtWidgets 模块导入所有内容，提供各种 GUI 组件
from qtpy.QtWidgets import *

# 从 src 模块导入 GET_PACKAGES 函数，此导入存在未使用问题
from src import GET_PACKAGES
# 从 src 模块导入 GET_PACKAGE_PATH 函数
from src import GET_PACKAGE_PATH

# 从 src.Core.Common 模块导入所有内容，此导入存在未使用问题
from src.Core.Common import *
# 从 src.UI.Canvas.UICommon 模块导入所有内容
from src.UI.Canvas.UICommon import *
# 从 src.UI.EditorHistory 模块导入 EditorHistory 类
from src.UI.EditorHistory import EditorHistory
# 从 src.Core.NodeBase 模块导入 NodeBase 类
from src.Core.NodeBase import NodeBase

# 从 src.UI.Utils.stylesheet 模块导入 editableStyleSheet 函数
from src.UI.Utils.stylesheet import editableStyleSheet


class NodeBoxLineEdit(QLineEdit):
    """
    NodeBoxLineEdit 类继承自 QLineEdit，用于在节点框中提供一个输入框。
    该输入框可以设置样式、占位文本等。
    """
    def __init__(self, parent, events=True):
        """
        初始化 NodeBoxLineEdit 实例。

        :param parent: 父控件
        :param events: 是否启用事件，默认为 True
        """
        # 调用父类 QLineEdit 的构造函数
        super(NodeBoxLineEdit, self).__init__(parent)
        # 设置父控件
        self.setParent(parent)
        # 记录事件启用状态
        self._events = events
        # 保存父控件引用
        self.parent = parent
        # 设置区域设置，此处存在类 'QLocale' 的未解析的特性引用问题
        self.setLocale(
            QtCore.QLocale(QtCore.QLocale.English, QtCore.QLocale.UnitedStates)
        )
        # 设置对象名称
        self.setObjectName("le_nodes")
        # 定义输入框的样式
        style = (
            "border-radius: 2px;"
            + "font-size: 14px;"
            + "border-style: outset;"
            + "border-width: 1px;"
        )
        # 设置输入框的样式表
        self.setStyleSheet(style)
        # 设置输入框的占位文本
        self.setPlaceholderText("enter node name..")


class NodeBoxTreeWidget(QTreeWidget):
    """
    NodeBoxTreeWidget 类继承自 QTreeWidget，用于在节点框中显示节点树。
    支持节点的插入、刷新、拖拽等功能，并且可以根据节点信息显示或隐藏提示信息。
    """
    # 定义信号，用于在需要显示节点信息时发出信号
    showInfo = QtCore.Signal(object)
    # 定义信号，用于在需要隐藏节点信息时发出信号
    hideInfo = QtCore.Signal()

    def __init__(
        self,
        parent,
        canvas,
        bNodeInfoEnabled=True,
        useDragAndDrop=True,
        bGripsEnabled=True,
    ):
        """
        初始化 NodeBoxTreeWidget 实例。

        :param parent: 父控件
        :param canvas: 画布对象
        :param bNodeInfoEnabled: 是否启用节点信息显示，默认为 True
        :param useDragAndDrop: 是否启用拖拽功能，默认为 True
        :param bGripsEnabled: 是否启用尺寸调整手柄，默认为 True
        """
        # 调用父类 QTreeWidget 的构造函数
        super(NodeBoxTreeWidget, self).__init__(parent)
        # 定义树控件的样式
        style = (
            "border-radius: 2px;"
            + "font-size: 14px;"
            + "border-style: outset;"
            + "border-width: 1px;"
        )
        # 设置树控件的样式表
        self.setStyleSheet(style)
        # 记录尺寸调整手柄启用状态
        self.bGripsEnabled = bGripsEnabled
        # 保存画布对象引用
        self.canvas = canvas
        # 设置父控件
        self.setParent(parent)
        # 设置树控件的框架形状，此处存在类 'QFrame' 的未解析的特性引用问题
        self.setFrameShape(QFrame.NoFrame)
        # 设置树控件的框架阴影，此处存在类 'QFrame' 的未解析的特性引用问题
        self.setFrameShadow(QFrame.Sunken)
        # 设置对象名称
        self.setObjectName("tree_nodes")
        # 启用树控件的排序功能
        self.setSortingEnabled(True)
        # 按指定列和排序顺序对树控件进行排序，此处存在类 'Qt' 的未解析的特性引用问题
        self.sortByColumn(0, QtCore.Qt.AscendingOrder)
        # 设置树控件的列数
        self.setColumnCount(0)
        # 隐藏树控件的表头
        self.setHeaderHidden(True)
        # 记录拖拽功能启用状态
        self.bUseDragAndDrop = useDragAndDrop
        if useDragAndDrop:
            # 启用树控件的拖拽功能
            self.setDragEnabled(True)
            # 设置树控件的拖拽模式，此处存在类 'QAbstractItemView' 的未解析的特性引用问题
            self.setDragDropMode(QAbstractItemView.DragOnly)
        # 启用树控件的动画效果
        self.setAnimated(True)
        # 存储节点分类路径和对应树项的字典
        self.categoryPaths = {}
        # 记录节点信息显示启用状态
        self.bNodeInfoEnabled = bNodeInfoEnabled
        # 连接当前选中项改变信号到相应的处理方法
        self.currentItemChanged.connect(self.onCurrentItemChanged)
        # 记录建议功能启用状态
        self.suggestionsEnabled = False

    def onCurrentItemChanged(self, current, previous):
        """
        处理当前选中项改变的事件。
        根据当前选中项是否为分类节点以及节点信息是否启用，决定显示或隐藏节点信息。

        :param current: 当前选中的项
        :param previous: 之前选中的项
        """
        if current is not None:
            if self.bNodeInfoEnabled:
                # 此处存在类 'QTreeWidgetItem' 的未解析的特性引用问题
                if not current.bCategory:
                    if current.docString is not None:
                        # 发出显示节点信息的信号
                        self.showInfo.emit(current.docString)
                else:
                    # 发出隐藏节点信息的信号
                    self.hideInfo.emit()

    def _isCategoryExists(self, category_name, categories):
        """
        检查指定的分类名称是否存在于给定的分类列表中。

        :param category_name: 要检查的分类名称
        :param categories: 分类列表
        :return: 如果存在返回 True，否则返回 False
        """
        bFound = False
        if category_name in categories:
            return True
        if not bFound:
            for c in categories:
                # 分割分类名称
                sepCatNames = c.split("|")
                if len(sepCatNames) == 1:
                    if category_name == c:
                        return True
                else:
                    for i in range(0, len(sepCatNames)):
                        c = "|".join(sepCatNames)
                        if category_name == c:
                            return True
                        # 移除最后一个分割项
                        sepCatNames.pop()
        return False

    def insertNode(
        self,
        nodeCategoryPath,
        name,
        doc=None,
        libName=None,
        bPyNode=False,
        bCompoundNode=False,
    ):
        """
        向树控件中插入一个节点项。
        根据节点的分类路径创建相应的分类节点，并将节点项添加到对应的分类下。

        :param nodeCategoryPath: 节点的分类路径
        :param name: 节点的名称
        :param doc: 节点的文档字符串，默认为 None
        :param libName: 节点所属的库名称，默认为 None
        :param bPyNode: 是否为 Python 节点，默认为 False
        :param bCompoundNode: 是否为复合节点，默认为 False
        :return: 插入的节点项对象
        """
        # 分割节点分类路径
        nodePath = nodeCategoryPath.split("|")
        categoryPath = ""
        # 遍历节点分类路径，创建分类节点
        for folderId in range(0, len(nodePath)):
            folderName = nodePath[folderId]
            if folderId == 0:
                categoryPath = folderName
                if categoryPath not in self.categoryPaths:
                    # 创建根分类节点项
                    rootFolderItem = QTreeWidgetItem(self)
                    # 标记为分类节点，此处存在动态属性问题
                    rootFolderItem.bCategory = True
                    # 设置节点项的标志，此处存在类 'Qt' 的未解析的特性引用问题
                    rootFolderItem.setFlags(QtCore.Qt.ItemIsEnabled)
                    # 设置节点项的文本
                    rootFolderItem.setText(0, folderName)
                    # 设置节点项的背景颜色
                    rootFolderItem.setBackground(
                        folderId, editableStyleSheet().BgColorBright
                    )
                    # 将分类路径和对应节点项存入字典
                    self.categoryPaths[categoryPath] = rootFolderItem
            else:
                parentCategoryPath = categoryPath
                categoryPath += "|{}".format(folderName)
                if categoryPath not in self.categoryPaths:
                    # 创建子分类节点项
                    childCategoryItem = QTreeWidgetItem(
                        self.categoryPaths[parentCategoryPath]
                    )
                    # 设置节点项的标志，此处存在类 'Qt' 的未解析的特性引用问题
                    childCategoryItem.setFlags(QtCore.Qt.ItemIsEnabled)
                    # 标记为分类节点，此处存在动态属性问题
                    childCategoryItem.bCategory = True
                    # 设置节点项的文本
                    childCategoryItem.setText(0, folderName)
                    # 设置节点项的背景颜色
                    childCategoryItem.setBackground(
                        0, editableStyleSheet().BgColorBright.lighter(150)
                    )
                    # 将分类路径和对应节点项存入字典
                    self.categoryPaths[categoryPath] = childCategoryItem
        # 在构建好的分类下创建节点项
        # 注意：此处动态创建属性的方式不够优雅，建议子类化 QTreeWidgetItem
        nodeItem = QTreeWidgetItem(self.categoryPaths[categoryPath])
        # 标记为非分类节点，此处存在动态属性问题
        nodeItem.bCategory = False
        # 标记是否为 Python 节点，此处存在动态属性问题
        nodeItem.bPyNode = bPyNode
        # 标记是否为复合节点，此处存在动态属性问题
        nodeItem.bCompoundNode = bCompoundNode
        # 设置节点项的文本
        nodeItem.setText(0, name)
        # 设置节点项所属的库名称，此处存在动态属性问题
        nodeItem.libName = libName
        # 设置节点项的文档字符串
        nodeItem.docString = doc
        return nodeItem

    def refresh(self, pattern="", pinDirection=None, pinStructure=StructureType.Single):
        """
        刷新树控件中的节点项。
        清空现有节点项，根据指定的模式、引脚方向和引脚结构重新插入节点项。

        :param pattern: 过滤节点的模式，默认为空字符串
        :param pinDirection: 引脚方向，默认为 None
        :param pinStructure: 引脚结构，默认为 StructureType.Single
        """
        # 清空树控件中的所有节点项
        self.clear()
        # 清空分类路径字典
        self.categoryPaths = {}

        # 初始化数据类型变量，用于存储当前按下引脚的数据类型
        dataType = None
        # 检查画布上是否有按下的引脚
        if self.canvas.pressedPin is not None:
            # 获取当前按下的引脚的数据类型
            dataType = self.canvas.pressedPin.dataType

        # 遍历所有包
        for package_name, package in GET_PACKAGES().items():
            # 处理注解函数
            for libName, lib in package.GetFunctionLibraries().items():
                # 获取库中的所有函数
                foos = lib.getFunctions()
                for name, foo in foos.items():
                    # 这里重复赋值，可考虑移除，保留原有代码
                    foo = foo
                    # 获取函数注解中的库名称
                    libName = foo.__annotations__["lib"]
                    # 获取函数的参数名称
                    fooArgNames = getfullargspec(foo).args
                    # 存储函数输入类型的集合
                    fooInpTypes = set()
                    # 存储函数输出类型的集合
                    fooOutTypes = set()
                    # 存储函数输入结构的集合
                    fooInpStructs = set()
                    # 存储函数输出结构的集合
                    fooOutStructs = set()
                    # 检查函数注解中的节点类型是否为可调用类型
                    if foo.__annotations__["nodeType"] == NodeTypes.Callable:
                        # 如果是可调用节点类型，添加执行引脚类型和结构
                        fooInpTypes.add("ExecPin")
                        fooOutTypes.add("ExecPin")
                        fooInpStructs.add(StructureType.Single)
                        fooOutStructs.add(StructureType.Single)

                    # 考虑函数的返回类型
                    if foo.__annotations__["return"] is not None:
                        # 添加返回类型和结构
                        fooOutTypes.add(foo.__annotations__["return"][0])
                        fooOutStructs.add(
                            findStructFromValue(foo.__annotations__["return"][1])
                        )

                    # 遍历函数的参数名称
                    for index in range(len(fooArgNames)):
                        # 获取参数的注解类型
                        dType = foo.__annotations__[fooArgNames[index]]
                        # 如果是元组，表示引用引脚类型（输出）和默认值
                        # 例如：(3, True) - 布尔类型，默认值为 True
                        fooInpTypes.add(dType[0])
                        fooInpStructs.add(findStructFromValue(dType[1]))

                    # 构建节点的分类路径
                    nodeCategoryPath = "{0}|{1}".format(
                        package_name, foo.__annotations__["meta"][NodeMeta.CATEGORY]
                    )
                    # 获取函数注解中的关键词
                    keywords = foo.__annotations__["meta"][NodeMeta.KEYWORDS]
                    # 构建用于检查的字符串，包含节点名称、分类路径和关键词
                    checkString = name + nodeCategoryPath + "".join(keywords)
                    # 检查过滤模式是否在检查字符串中
                    if pattern.lower() in checkString.lower():
                        # 如果点击在画布上，创建所有节点项
                        if dataType is None:
                            # 关闭建议功能
                            self.suggestionsEnabled = False
                            # 插入节点项
                            self.insertNode(
                                nodeCategoryPath, name, foo.__doc__, libName
                            )
                        else:
                            # 开启建议功能
                            self.suggestionsEnabled = True
                            # 检查引脚方向是否为输出
                            if pinDirection == PinDirection.Output:
                                if pinStructure != StructureType.Multi:
                                    # 检查是否有多个引脚
                                    hasMultiPins = StructureType.Multi in fooInpStructs
                                    if dataType in fooInpTypes and (
                                        pinStructure in fooInpStructs or hasMultiPins
                                    ):
                                        # 插入节点项
                                        self.insertNode(
                                            nodeCategoryPath, name, foo.__doc__, libName
                                        )
                                elif dataType in fooInpTypes:
                                    # 插入节点项
                                    self.insertNode(
                                        nodeCategoryPath, name, foo.__doc__, libName
                                    )
                            else:
                                if pinStructure != StructureType.Multi:
                                    # 检查是否有多个引脚
                                    hasMultiPins = StructureType.Multi in fooOutStructs
                                    if dataType in fooOutTypes and (
                                        pinStructure in fooOutStructs or hasMultiPins
                                    ):
                                        # 插入节点项
                                        self.insertNode(
                                            nodeCategoryPath, name, foo.__doc__, libName
                                        )
                                elif dataType in fooOutTypes:
                                    # 插入节点项
                                    self.insertNode(
                                        nodeCategoryPath, name, foo.__doc__, libName
                                    )

            # 处理基于类的节点
            for node_class in package.GetNodeClasses().values():
                if node_class.__name__ in ("setVar", "getVar"):
                    # 跳过特定名称的节点类
                    continue

                # 构建节点的分类路径
                nodeCategoryPath = "{0}|{1}".format(package_name, node_class.category())

                # 构建用于检查的字符串，包含节点类名、分类路径和关键词
                checkString = (
                    node_class.__name__
                    + nodeCategoryPath
                    + "".join(node_class.keywords())
                )
                # 检查过滤模式是否不在检查字符串中，如果不在则跳过
                if pattern.lower() not in checkString.lower():
                    continue
                if dataType is None:
                    # 插入节点项
                    self.insertNode(
                        nodeCategoryPath, node_class.__name__, node_class.description()
                    )
                else:
                    # 如果按下的引脚是输出引脚，根据节点输入类型过滤
                    # 获取节点类的引脚类型提示信息
                    hints = node_class.pinTypeHints()
                    if pinDirection == PinDirection.Output:
                        if pinStructure != StructureType.Multi:
                            # 检查是否有多个引脚
                            hasMultiPins = StructureType.Multi in hints.inputStructs
                            # 检查数据类型是否在输入类型中，且引脚结构匹配
                            if dataType in hints.inputTypes and (
                                pinStructure in hints.inputStructs or hasMultiPins
                            ):
                                # 插入节点项
                                self.insertNode(
                                    nodeCategoryPath,
                                    node_class.__name__,
                                    node_class.description(),
                                )
                        elif dataType in hints.inputTypes:
                            # 插入节点项
                            self.insertNode(
                                nodeCategoryPath,
                                node_class.__name__,
                                node_class.description(),
                            )
                    else:
                        # 如果按下的引脚是输入引脚，根据节点输出类型过滤
                        if pinStructure != StructureType.Multi:
                            # 检查是否有多个引脚
                            hasMultiPins = StructureType.Multi in hints.outputStructs
                            # 检查数据类型是否在输出类型中，且引脚结构匹配
                            if dataType in hints.outputTypes and (
                                pinStructure in hints.outputStructs or hasMultiPins
                            ):
                                # 插入节点项
                                self.insertNode(
                                    nodeCategoryPath,
                                    node_class.__name__,
                                    node_class.description(),
                                )
                        elif dataType in hints.outputTypes:
                            # 插入节点项
                            self.insertNode(
                                nodeCategoryPath,
                                node_class.__name__,
                                node_class.description(),
                            )

            # 填充导出的 Python 节点
            # 获取包的路径
            packagePath = GET_PACKAGE_PATH(package_name)
            # 构建 Python 节点的根目录路径
            pyNodesRoot = os.path.join(packagePath, "PyNodes")
            # 检查 Python 节点根目录是否存在
            if os.path.exists(pyNodesRoot):
                # 遍历 Python 节点根目录及其子目录
                for path, dirs, files in os.walk(pyNodesRoot):
                    for f in files:
                        # 分割文件名和扩展名
                        pyNodeName, extension = os.path.splitext(f)
                        # 检查文件扩展名是否为 .pynode
                        if extension == ".pynode":
                            # 规范化路径
                            p = os.path.normpath(path)
                            # 分割路径为文件夹列表
                            folders = p.split(os.sep)
                            # 找到 "PyNodes" 文件夹的索引
                            index = folders.index("PyNodes")
                            # 构建分类后缀
                            categorySuffix = "|".join(folders[index:])
                            # 构建分类路径
                            category = "{0}|{1}".format(package_name, categorySuffix)
                            # 插入 Python 节点项
                            self.insertNode(category, pyNodeName, bPyNode=True)

            # 填充导出的复合节点
            # 构建复合节点的根目录路径
            compoundNodesRoot = os.path.join(packagePath, "Compounds")
            # 检查复合节点根目录是否存在
            if os.path.exists(compoundNodesRoot):
                # 遍历复合节点根目录及其子目录
                for path, dirs, files in os.walk(compoundNodesRoot):
                    for f in files:
                        # 分割文件名和扩展名
                        _, extension = os.path.splitext(f)
                        # 检查文件扩展名是否为 .compound
                        if extension == ".compound":
                            # 规范化复合节点根路径
                            compoundsRoot = os.path.normpath(path)
                            # 构建复合节点的完整路径
                            fullCompoundPath = os.path.join(compoundsRoot, f)
                            # 打开复合节点文件
                            with open(fullCompoundPath, "r") as compoundFile:
                                # 加载复合节点的 JSON 数据
                                data = json.load(compoundFile)
                                # 获取复合节点的分类名称
                                compoundCategoryName = data["category"]
                                # 获取复合节点的名称
                                compoundNodeName = data["name"]
                                # 构建分类路径
                                category = "{0}|{1}|{2}".format(
                                    package_name, "Compounds", compoundCategoryName
                                )
                                # 插入复合节点项
                                self.insertNode(
                                    category, compoundNodeName, bCompoundNode=True
                                )

            # 展开所有分类节点
            if dataType is not None:
                for categoryItem in self.categoryPaths.values():
                    # 展开分类节点
                    categoryItem.setExpanded(True)
            # 对树控件中的项进行排序，此处存在类 'Qt' 的未解析的特性引用问题
            self.sortItems(0, QtCore.Qt.AscendingOrder)

    def mousePressEvent(self, event):
        """
        处理鼠标按下事件。
        根据点击的节点项类型创建节点或进行拖拽操作。

        :param event: 鼠标按下事件对象
        """
        # 调用父类的鼠标按下事件处理方法
        super(NodeBoxTreeWidget, self).mousePressEvent(event)
        # 获取当前点击的节点项
        item_clicked = self.currentItem()
        if not item_clicked:
            # 如果没有点击到节点项，忽略事件并返回
            event.ignore()
            return
        # 检查点击的节点项是否为分类节点，此处存在类 'QTreeWidgetItem' 的未解析的特性引用问题
        if item_clicked.bCategory:
            # 如果是分类节点，忽略事件并返回
            event.ignore()
            return
        # 找到顶层父节点
        rootItem = item_clicked
        bPyNode = False
        bCompoundNode = False
        # 此处存在类 'QTreeWidgetItem' 的未解析的特性引用问题
        if not item_clicked.bCategory:
            bPyNode = rootItem.bPyNode
            bCompoundNode = rootItem.bCompoundNode
        while not rootItem.parent() is None:
            rootItem = rootItem.parent()
            # 此处存在类 'QTreeWidgetItem' 的未解析的特性引用问题
            if not rootItem.bCategory:
                bPyNode = rootItem.bPyNode
                bCompoundNode = rootItem.bCompoundNode
        # 获取顶层父节点的文本作为包名
        packageName = rootItem.text(0)
        # 获取点击节点项的文本
        pressed_text = item_clicked.text(0)
        # 获取点击节点项所属的库名称，此处存在类 'QTreeWidgetItem' 的未解析的特性引用问题
        libName = item_clicked.libName

        if pressed_text in self.categoryPaths.keys():
            # 如果点击的是分类路径，忽略事件并返回
            event.ignore()
            return

        # 获取节点的 JSON 模板
        jsonTemplate = NodeBase.jsonTemplate()
        # 设置 JSON 模板中的包名
        jsonTemplate["package"] = packageName
        # 设置 JSON 模板中的库名
        jsonTemplate["lib"] = libName
        # 设置 JSON 模板中的节点类型
        jsonTemplate["type"] = pressed_text
        # 设置 JSON 模板中的节点名称
        jsonTemplate["name"] = pressed_text
        # 设置 JSON 模板中的节点唯一标识符
        jsonTemplate["uuid"] = str(uuid.uuid4())
        # 设置 JSON 模板中的节点标签
        jsonTemplate["meta"]["label"] = pressed_text
        # 设置 JSON 模板中的是否为 Python 节点标志
        jsonTemplate["bPyNode"] = bPyNode
        # 设置 JSON 模板中的是否为复合节点标志
        jsonTemplate["bCompoundNode"] = bCompoundNode

        if self.canvas.pressedPin is not None and self.bGripsEnabled:
            # 将画布上的鼠标释放位置转换为场景位置
            a = self.canvas.mapToScene(self.canvas.mouseReleasePos)
            # 设置 JSON 模板中的节点 x 坐标
            jsonTemplate["x"] = a.x()
            # 设置 JSON 模板中的节点 y 坐标
            jsonTemplate["y"] = a.y()
            # 在画布上创建节点
            node = self.canvas.createNode(jsonTemplate)
            if bPyNode or bCompoundNode:
                # 重建节点
                node.rebuild()
            # 隐藏节点框
            self.canvas.hideNodeBox()
            # 获取当前按下的引脚
            pressedPin = self.canvas.pressedPin
            if pressedPin.direction == PinDirection.Input:
                # 如果按下的引脚是输入引脚，尝试连接输出引脚
                for pin in node.UIoutputs.values():
                    # 在画布上内部连接引脚
                    wire = self.canvas.connectPinsInternal(pressedPin, pin)
                    if wire is not None:
                        # 保存操作状态到编辑器历史记录
                        EditorHistory().saveState("Connect pins", modify=True)
                        break
            if pressedPin.direction == PinDirection.Output:
                # 如果按下的引脚是输出引脚，尝试连接输入引脚
                for pin in node.UIinputs.values():
                    # 在画布上内部连接引脚
                    wire = self.canvas.connectPinsInternal(pin, pressedPin)
                    if wire is not None:
                        # 保存操作状态到编辑器历史记录
                        EditorHistory().saveState("Connect pins", modify=True)
                        break
        else:
            # 创建拖拽对象
            drag = QtGui.QDrag(self)
            # 创建 MIME 数据对象
            mime_data = QtCore.QMimeData()

            # 将 JSON 模板转换为字符串
            pressed_text = json.dumps(jsonTemplate)
            # 设置 MIME 数据的文本
            mime_data.setText(pressed_text)
            # 设置拖拽对象的 MIME 数据
            drag.setMimeData(mime_data)
            # 执行拖拽操作
            drag.exec()

    def update(self):
        """
        更新树控件中分类节点的背景颜色。
        此方法的签名与类 'QAbstractItemView' 中基方法的签名不匹配。
        """
        for category in self.categoryPaths.values():
            if not category.parent():
                # 如果是根分类节点，设置背景颜色
                category.setBackground(0, editableStyleSheet().BgColorBright)
            else:
                # 如果是子分类节点，设置背景颜色
                category.setBackground(
                    0, editableStyleSheet().BgColorBright.lighter(150)
                )
        # 注释掉的代码，调用父类的 update 方法
        #super(NodeBoxTreeWidget, self).update()


class NodeBoxSizeGrip(QSizeGrip):
    """
    NodeBoxSizeGrip 类继承自 QSizeGrip，用于在节点框中提供尺寸调整手柄。
    可以自定义手柄的大小和绘制样式。
    """
    def __init__(self, parent=None):
        """
        初始化 NodeBoxSizeGrip 实例。

        :param parent: 父控件，默认为 None
        """
        # 调用父类 QSizeGrip 的构造函数
        super(NodeBoxSizeGrip, self).__init__(parent)

    def sizeHint(self):
        """
        返回尺寸调整手柄的建议大小。

        :return: 建议的大小，为 QSize 对象
        """
        return QtCore.QSize(13, 13)

    def paintEvent(self, event):
        """
        处理绘制事件，绘制尺寸调整手柄。
        此处存在类 'QPainter' 的未解析的特性引用问题。

        :param event: 绘制事件对象
        """
        # 创建 QPainter 对象
        painter = QtGui.QPainter()
        # 开始绘制
        painter.begin(self)
        # 设置绘制提示，启用抗锯齿功能
        painter.setRenderHint(QtGui.QPainter.Antialiasing)
        # 获取绘制事件的矩形区域
        rect = event.rect()
        # 设置画笔的画刷颜色
        painter.setBrush(QtGui.QColor(80, 80, 80, 255))
        # 绘制圆角矩形
        painter.drawRoundedRect(rect, 3, 3)
        # 绘制像素图
        painter.drawPixmap(rect, QtGui.QPixmap(":resize_diagonal.png"))
        # 结束绘制
        painter.end()


class NodesBox(QFrame):
    """
    NodesBox 类继承自 QFrame，用于创建一个包含节点树和节点信息显示的框。
    支持节点搜索、拖拽、尺寸调整等功能。
    """
    def __init__(
        self,
        parent,
        canvas,
        bNodeInfoEnabled=True,
        bGripsEnabled=True,
        bUseDragAndDrop=False,
    ):
        """
        初始化 NodesBox 实例。

        :param parent: 父控件
        :param canvas: 画布对象
        :param bNodeInfoEnabled: 是否启用节点信息显示，默认为 True
        :param bGripsEnabled: 是否启用尺寸调整手柄，默认为 True
        :param bUseDragAndDrop: 是否启用拖拽功能，默认为 False
        """
        # 调用父类 QFrame 的构造函数
        super(NodesBox, self).__init__(parent)
        # 设置控件的内容边距
        self.setContentsMargins(0, 0, 0, 0)
        # 设置控件的框架样式，此处存在类 'QFrame' 的未解析的特性引用问题
        self.setFrameStyle(QFrame.Panel | QFrame.Raised)
        # 标记是否正在拖拽
        self.bDragging = False
        # 记录鼠标的最后位置
        self.lastCursorPos = QtCore.QPoint(0, 0)
        # 记录鼠标的偏移量
        self.offset = QtCore.QPoint(0, 0)
        # 启用鼠标跟踪功能
        self.setMouseTracking(True)
        # 创建垂直布局
        self.mainLayout = QVBoxLayout(self)
        # 设置布局的对象名称
        self.mainLayout.setObjectName("mainLayout")
        # 设置布局的间距
        self.mainLayout.setSpacing(1)
        # 设置布局的内容边距
        self.mainLayout.setContentsMargins(1, 1, 1, 1)
        # 创建分割器
        self.splitter = QSplitter()
        # 设置分割器的对象名称
        self.splitter.setObjectName("nodeBoxSplitter")
        # 将分割器添加到主布局中
        self.mainLayout.addWidget(self.splitter)
        # 记录尺寸调整手柄启用状态
        self.bGripsEnabled = bGripsEnabled
        if self.bGripsEnabled:
            # 创建尺寸调整手柄
            self.sizeGrip = NodeBoxSizeGrip(self)
            # 设置尺寸调整手柄的对象名称
            self.sizeGrip.setObjectName("nodeBoxSizeGrip")
            # 创建水平布局
            self.sizeGripLayout = QHBoxLayout()
            # 设置布局的对象名称
            self.sizeGripLayout.setObjectName("sizeGripLayout")
            # 设置布局的间距
            self.sizeGripLayout.setSpacing(1)
            # 设置布局的内容边距
            self.sizeGripLayout.setContentsMargins(1, 1, 1, 1)
            # 创建间隔项，此处存在类 'QSizePolicy' 的未解析的特性引用问题
            spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
            # 将间隔项添加到布局中
            self.sizeGripLayout.addItem(spacerItem)
            # 将尺寸调整手柄添加到布局中
            self.sizeGripLayout.addWidget(self.sizeGrip)
            # 将布局添加到主布局中
            self.mainLayout.addLayout(self.sizeGripLayout)

        # 创建节点树控件的容器
        self.nodeTreeWidget = QWidget()
        # 设置节点树控件容器的对象名称
        self.nodeTreeWidget.setObjectName("nodeTreeWidget")
        # 设置节点树控件容器的内容边距
        self.nodeTreeWidget.setContentsMargins(0, 0, 0, 0)
        # 创建垂直布局
        self.verticalLayout = QVBoxLayout(self.nodeTreeWidget)
        # 设置布局的对象名称
        self.verticalLayout.setObjectName("verticalLayout")
        # 设置布局的间距
        self.verticalLayout.setSpacing(1)
        # 设置布局的内容边距
        self.verticalLayout.setContentsMargins(1, 1, 1, 1)
        # 将节点树控件容器添加到分割器中
        self.splitter.addWidget(self.nodeTreeWidget)
        # 创建节点框输入框
        self.lineEdit = NodeBoxLineEdit(self)
        # 设置输入框的对象名称
        self.lineEdit.setObjectName("lineEdit")
        # 将输入框添加到布局中
        self.verticalLayout.addWidget(self.lineEdit)
        # 连接输入框的文本改变信号到相应的处理方法
        self.lineEdit.textChanged.connect(self.leTextChanged)
        # 创建节点信息显示的文本浏览器
        self.nodeInfoWidget = QTextBrowser()
        # 设置文本浏览器的焦点策略，此处存在类 'Qt' 的未解析的特性引用问题
        self.nodeInfoWidget.setFocusPolicy(QtCore.Qt.NoFocus)
        # 设置文本浏览器的对象名称
        self.nodeInfoWidget.setObjectName("nodeBoxInfoBrowser")
        # 启用文本浏览器的外部链接打开功能
        self.nodeInfoWidget.setOpenExternalLinks(True)
        # 将文本浏览器添加到分割器中
        self.splitter.addWidget(self.nodeInfoWidget)
        # 重复添加文本浏览器，可能是错误
        self.splitter.addWidget(self.nodeInfoWidget)
        # 根据节点信息显示启用状态设置文本浏览器的可见性
        self.nodeInfoWidget.setVisible(bNodeInfoEnabled)

        # 创建节点框树控件
        self.treeWidget = NodeBoxTreeWidget(
            self, canvas, bNodeInfoEnabled, bUseDragAndDrop, bGripsEnabled
        )
        # 设置节点框树控件的对象名称
        self.treeWidget.setObjectName("nodeBoxTreeWidget")
        # 设置节点框树控件表头项的文本
        self.treeWidget.headerItem().setText(0, "1")
        # 将节点框树控件添加到布局中
        self.verticalLayout.addWidget(self.treeWidget)
        # 刷新节点框树控件
        self.treeWidget.refresh()

        # 连接节点框树控件的显示信息信号到相应的处理方法
        self.treeWidget.showInfo.connect(self.onShowInfo)
        # 连接节点框树控件的隐藏信息信号到相应的处理方法
        self.treeWidget.hideInfo.connect(self.onHideInfo)

    def hideEvent(self, event):
        """
        处理控件隐藏事件，设置拖拽标志为 False。

        :param event: 隐藏事件对象
        """
        self.bDragging = False

    def showEvent(self, event):
        """
        处理控件显示事件，清空节点信息显示并设置拖拽标志为 False。

        :param event: 显示事件对象
        """
        self.nodeInfoWidget.setHtml("")
        self.bDragging = False

    def onShowInfo(self, restructuredText):
        """
        处理显示节点信息的信号，显示节点信息。

        :param restructuredText: 要显示的节点信息文本
        """
        # 显示节点信息文本浏览器
        self.nodeInfoWidget.show()
        # 将节点信息文本转换为 HTML 并设置到文本浏览器中
        self.nodeInfoWidget.setHtml(rst2html(restructuredText))

    def onHideInfo(self):
        """
        处理隐藏节点信息的信号，清空节点信息显示。
        """
        # 清空节点信息文本浏览器的内容
        self.nodeInfoWidget.setHtml("")

    def sizeHint(self):
        """
        返回控件的建议大小。

        :return: 建议的大小，为 QSize 对象
        """
        return QtCore.QSize(500, 300)

    def expandCategory(self):
        """
        展开节点框树控件中的所有分类节点。
        """
        for i in range(self.treeWidget.topLevelItemCount()):
            # 获取顶层节点项
            item = self.treeWidget.topLevelItem(i)
            if item.text(0) in self.treeWidget.categoryPaths:
                # 获取节点项的索引
                index = self.treeWidget.indexFromItem(item)
                # 递归展开节点项
                self.treeWidget.expandRecursively(index)

    def leTextChanged(self):
        """
        处理节点框输入框的文本改变事件。
        根据输入框的文本刷新节点框树控件，并展开分类节点。
        """
        if self.lineEdit.text() == "":
            # 如果输入框文本为空，设置占位文本并刷新节点框树控件
            self.lineEdit.setPlaceholderText("enter node name..")
            self.treeWidget.refresh()
            return
        # 根据输入框的文本刷新节点框树控件
        self.treeWidget.refresh(self.lineEdit.text())
        # 展开分类节点
        self.expandCategory()

    def mousePressEvent(self, event):
        """
        处理鼠标按下事件，判断是否开始拖拽。

        :param event: 鼠标按下事件对象
        """
        # 调用父类的鼠标按下事件处理方法
        super(NodesBox, self).mousePressEvent(event)
        if self.bGripsEnabled:
            if event.pos().y() >= self.geometry().height() - 30:
                # 如果鼠标点击位置在控件底部附近，开始拖拽
                self.bDragging = True
                # 记录鼠标的当前位置
                self.lastCursorPos = QtGui.QCursor.pos()

    def mouseMoveEvent(self, event):
        """
        处理鼠标移动事件，如果正在拖拽则移动控件位置。

        :param event: 鼠标移动事件对象
        """
        # 调用父类的鼠标移动事件处理方法
        super(NodesBox, self).mouseMoveEvent(event)
        if self.bGripsEnabled:
            if self.bDragging:
                # 计算鼠标移动的偏移量
                delta = QtGui.QCursor.pos() - self.lastCursorPos
                # 获取控件的当前位置
                currentPos = self.pos()
                # 移动控件位置
                self.move(currentPos + delta)
                # 记录鼠标的当前位置
                self.lastCursorPos = QtGui.QCursor.pos()

    def mouseReleaseEvent(self, event):
        """
        处理鼠标释放事件，结束拖拽。

        :param event: 鼠标释放事件对象
        """
        # 调用父类的鼠标释放事件处理方法
        super(NodesBox, self).mouseReleaseEvent(event)
        # 结束拖拽
        self.bDragging = False